package emby.lang;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map.Entry;
import java.util.Set;

import emby.err.NameNotFoundError;
import emby.lang.stdlib.DefaultFunctions;
import emby.overload.OverloadDot;
import emby.overload.OverloadRead;

/**
 * Represents scopes/environments.
 * Essentially a dictionary that maps variable names to objects.
 * @author Justin
 *
 */
public class Scope  implements OverloadDot, Serializable{
	private static final long serialVersionUID = -5966249516845907216L;

	Scope superScope;
	Hashtable<String, Object> mem;
	boolean isglobal = false;

	/**
	 * Returns a globalscope.
	 */
	public Scope() {
		this(null);
		setVar("$globalscope", this);
		setVar("$gs", this);
		DefaultFunctions.addBuiltInFunctions(this);
		isglobal = true;
	}

	public Scope(Scope superscope) {
		superScope = superscope;
		isglobal = false;
		mem = new Hashtable<String, Object>();
		mem.put("$currentscope", this);
		mem.put("$cs", this);
	}


	public void addSubScope(Scope b) {
		b.superScope = this;
	}

	/**
	 * Sets a variables value within this particular scope.
	 * @param varname
	 * @param val
	 */
	public void setVar(String varname, Object val){
		if(val==null) {
			mem.put(varname, EmbyNull.Null);
		}else
		mem.put(varname, val);
	}

	/**
	 * Gets a variable's value. If it is not found in this scope, checks the superscope.
	 * @param varname
	 * @param varname
	 * @return the value of the variable.
	 */
	public Object getVar(int line, String varname){
		Object o = mem.get(varname);
		if(o == null){ //if the varname does not exist, check superBlock
			if( superScope == null){ //if this IS the superblock, then return null
				throw new NameNotFoundError(line, varname+" is not defined within scope");
				//return null;
			}
			return superScope.getVar(line, varname);
		}
		if( o instanceof OverloadRead){
			return ((OverloadRead) o).operatorRead(line);
		}
		return o;
	}
	
	/**
	 * Removes a variable from this scope
	 * @param varname
	 * @return the value that the variable held, or EmbyNull if the variable doesnt exist
	 */
	public Object remove(String varname) {
		Object ret = mem.remove(varname);
		if( ret==null) {
			return EmbyNull.Null;
		}
		return ret;
	}
	
	
	/**
	 * Returns if this scope contains the varname. Does not check superscopes.
	 * @param varname
	 * @return
	 */
	public boolean contains(String varname) {
		return mem.containsKey(varname);
	}
	
	
	/**
	 * Returns if this scope contains the varname. Checks superscopes.
	 * @param varname
	 * @return
	 */
	public boolean containsSuper(String varname) {
		if(! mem.containsKey(varname)) {
			if( superScope==null) {
				return false;
			}
			return superScope.containsSuper(varname);
		}
		return true;
	}

	public Scope clone(){
		Scope s = new Scope(null);
		s.mem = mem;
		s.superScope = superScope;
		return s;
	}

	/**
	 * Dump the contents of s into target scope
	 * @param s
	 */
	public static void dump( Scope target, Scope s){
		Enumeration<String> keys = s.mem.keys();
		Enumeration<Object> vals = s.mem.elements();
		while( keys.hasMoreElements()){
			target.setVar(  keys.nextElement(), vals.nextElement());
		}
	}

	public Set<Entry<String,Object>> getEntries(){
		return mem.entrySet();
	}

	public String toString(){
		if(isglobal){
			return "global_scope";
		}
		else{
			return super.toString();
		}
	}

	@Override
	public Object get_dot(int line, String o) {
		return getVar(line,o);
	}

	@Override
	public void set_dot(int line, String o, Object new_value) {
		setVar(o,new_value);
	}
}

